/*
	GTA model importer creator for mtasa dm.
	This executable creates files which are needed due to limitations of mtasa dm.
	Created by The_GTA
*/
#include "StdInc.h"

#include <memory>

const char *compilator = NULL;

bool doCompile = false;
bool debug = false;
bool lodSupport = true;
bool forceLODInherit = false;
bool preserveMainWorldIntegrity = true;

unsigned short modelIDs[65536];
NameRegistry g_usedModelNames;

CIPL *ipls[256];
unsigned int numIPL = 0;
CIDE *ides[256];
unsigned int numIDE = 0;
bool modelLOD[65536];
IDE::CObject *avalID[65536];

instanceList_t instances;
IDE::objectList_t objects;
IDE::objectList_t lod;
lodMap_t lodMap;
std::map <unsigned short, IDE::CObject*> backLodMap;
unsigned int tableCount = 0;
unsigned int numAvailable = 0;

int mapXoffset=0;
int mapYoffset=0;
int mapZoffset=500;

eMappingType mappingType = MAPPING_XML;
eMappingFormat mappingFormat = MAPPING_SANANDREAS;

static bool zipOutput = false;
const char *zipName = NULL;
static bool zipResources = false;

static bool namesCaseInsensitive = true;

bool adjustFiltering = false;

// Collision registry for dynamic lookup.
CCollisionRegistry *colRegistry = NULL;

CFileTranslator *g_resourceRoot = NULL;
CFileTranslator *g_outputRoot = NULL;


bool CompareNames( const char *prim, const char *sec )
{
    if ( namesCaseInsensitive )
    {
        return ( stricmp( prim, sec ) == 0 );
    }
    else
    {
        return ( strcmp( prim, sec ) == 0 );
    }
}

IDE::CObject*	GetObjectByModel( const char *model )
{
	for ( IDE::CObject *obj : objects )
    {
		if ( CompareNames( obj->m_modelName, model ) )
        {
			return obj;
        }
    }

	return NULL;
}

// Process data
void	LoadTargetIPL(const filePath& filename)
{
    if ( numIPL == _countof(ipls) )
    {
        printf( "error: maximum number of IPL reached\n" );
        return;
    }

	CIPL *ipl = LoadIPL(filename);

	for ( const auto& keyPair : ipl->m_instances )
	{
        CInstance *inst = keyPair.second;

		if (!lodSupport)
		{
            unsigned int instIndex = inst->m_instIndex;

			if (ipl->IsLOD(instIndex))
				continue;
		}
		else if ( CInstance *lod = ipl->GetLod( inst ) )
        {
			lodMap[lod->m_modelID] = inst;
        }

		instances.push_back(inst);
	}

	ipls[numIPL++] = ipl;
}

void	LoadTargetIDE(const filePath& name)
{
	CIDE *ide = LoadIDE(name);

	if ( !ide )
	{
		throw converter_runtime_error( "could not load '" + std::string( name ) + "' file!" );
	}

	for ( IDE::CObject *objInst : ide->m_objects )
	{
        if ( objInst->m_modelID >= _countof(modelIDs) )
        {
            printf( "error: too big model id (%u)\n", objInst->m_modelID );
        }

		unsigned int m;

		// Assign the ID
		for ( m = 0; m < 65534; m++ )
		{
			if ( !avalID[m] )
				continue;

			avalID[m] = NULL;
			break;
		}
		if ( m == 65534 )
		{
			throw converter_runtime_error( "requiring more valid model ids" );
		}

		objInst->m_realModelID = m;
		modelIDs[objInst->m_modelID] = m;

		numAvailable--;

		if ( lodSupport )
		{
			//TODO: One lod to multiple objects!
			lodMap_t::const_iterator lodIter = lodMap.find(objInst->m_modelID);

			if ( lodIter != lodMap.end() )
			{
				lod.push_back(objInst);
				backLodMap[(*lodIter).second->m_modelID] = objInst;
			}
		}

		objects.push_back(objInst);
	}
}

void	LoadReplaceIPL(const filePath& filename)
{
	CIPL *ipl = LoadIPL(filename);
	unsigned int numInst = 0;

	for ( const auto& keyPair : ipl->m_instances )
    {
		modelLOD[ keyPair.second->m_modelID ] = ( ipl->IsLOD( numInst ) || keyPair.second->m_lod != -1 );
    }

	delete ipl;
}

void	LoadReplaceIDE(const filePath& filename, DynObjConfig *objconf, bool ignoreFlagsSecure)
{
	CIDE *ide = LoadIDE(filename);

	// Marks all ids as available
	for ( IDE::CObject *objInst : ide->m_objects )
	{
        if ( ignoreFlagsSecure )
        {
		    if ( objInst->m_flags &
                 (OBJECT_GRAFFITI | OBJECT_STATUE | OBJECT_UNKNOWN | OBJECT_UNKNOWN_2 |
                  OBJECT_ALPHA1 | OBJECT_ALPHA2 | OBJECT_BREAKGLASS | OBJECT_BREAKGLASS_CRACK |
                  OBJECT_GARAGE | OBJECT_MULTICLUMP | OBJECT_EXPLOSIVE | OBJECT_VEGETATION | OBJECT_BIG_VEGETATION | OBJECT_UNKNOWN_HIGH) )
		    //if ((*iter)->m_flags != 0 && !((*iter)->m_flags & OBJECT_INTERIOR))
			    continue;
        }

		if ( preserveMainWorldIntegrity && modelLOD[ objInst->m_modelID ] )
			continue;

        // We do not want to replace objects that have dynamic properties.
        if ( objconf != NULL && objconf->GetObjectAttributes( objInst->m_modelName ) != NULL )
            continue;

        if ( objInst->m_modelID < _countof(avalID) )
        {
		    avalID[ objInst->m_modelID ] = objInst;
		    numAvailable++;
        }
        else
        {
            printf( "error: too big model id (%u)\n", objInst->m_modelID );
        }
	}

    // We do not delete the IDE file, because we need access to its instances.
    // This is sort of a memory leak, topkek.
}

extern void RunMemoryTests( void );

// Entry
int		main (int argc, char *argv[])
{
#ifdef _DEBUG
    RunMemoryTests();
#endif //_DEBUG

    // Initialize the file system.
    {
        fs_construction_params constr;
        constr.nativeExecMan = NULL;

	    CFileSystem::Create( constr );
    }

    // Do the converter logic.
    bool success = false;
    {
	    std::unique_ptr <CINI> config;
	    CINI::Entry *mainEntry = NULL;
	    const char *mode = NULL;
        {
            std::unique_ptr <CFile> iniFilePtr( fileRoot->Open( "config.ini", "rb" ) );

            if ( iniFilePtr )
            {
                config.reset( LoadINI( iniFilePtr.get() ) );
            }
        }

        bool ignoreFlagsSecure;

	    if (config && (mainEntry = config->GetEntry("General")))
	    {
		    // Apply configuration
            compilator = mainEntry->Get("compilator");
		    doCompile = mainEntry->GetBool("compile", true);
		    debug = mainEntry->GetBool("debug", false);
		    mode = mainEntry->Get("mode");
		    lodSupport = mainEntry->GetBool("lodSupport", true);
		    preserveMainWorldIntegrity = mainEntry->GetBool("preserveMainWorldIntegrity", false);
		    mapXoffset = mainEntry->GetInt("xOffset", 0);
		    mapYoffset = mainEntry->GetInt("yOffset", 0);
		    mapZoffset = mainEntry->GetInt("zOffset", 0);
            zipOutput = mainEntry->GetBool("zipOutput", false);
            zipName = mainEntry->Get("zipName");
            zipResources = mainEntry->GetBool("zipResources", false);
            namesCaseInsensitive = mainEntry->GetBool("namesCaseInsensitive", true);

            const char *mapType = mainEntry->Get("mappingType");

            if ( mapType != NULL && stricmp( mapType, "xml" ) == 0 )
            {
                mappingType = MAPPING_XML;
            }
            else if ( mapType != NULL && stricmp( mapType, "lua" ) == 0 )
            {
                mappingType = MAPPING_LUA;
            }
            else
            {
                mappingType = MAPPING_XML;
            }

            const char *formatType = mainEntry->Get("mapFormat");

            if ( formatType != NULL && ( stricmp( formatType, "iii" ) == 0 || stricmp( formatType, "3" ) == 0 ) )
            {
                mappingFormat = MAPPING_III;
            }
            else if ( formatType != NULL && ( stricmp( formatType, "vc" ) == 0 || stricmp( formatType, "vicecity" ) == 0 ) )
            {
                mappingFormat = MAPPING_VICECITY;
            }
            else if ( formatType != NULL && ( stricmp( formatType, "sa" ) == 0 || stricmp( formatType, "sanandreas" ) == 0 ) )
            {
                mappingFormat = MAPPING_SANANDREAS;
            }
            else
            {
                mappingFormat = MAPPING_SANANDREAS;
            }

            ignoreFlagsSecure = mainEntry->GetBool("ignoreFlagsSecure", true);
            adjustFiltering = mainEntry->GetBool("adjustFiltering", false);
	    }
	    else
	    {
		    // Defaults
            compilator = NULL;
		    doCompile = true;
		    debug = false;
		    mode = "green";
		    lodSupport = true;
		    preserveMainWorldIntegrity = false;
            zipOutput = false;
            zipName = NULL;
            zipResources = false;
            namesCaseInsensitive = true;
            mappingType = MAPPING_XML;
            mappingFormat = MAPPING_SANANDREAS;
            ignoreFlagsSecure = true;
            adjustFiltering = false;
	    }

        // Protect the runtime block.
        try
        {
	        if ( preserveMainWorldIntegrity )
		        printf( "Compiling with main world integrity...\n" );

	        // Reset the IDs
	        for ( unsigned int n = 0; n < 65536; n++ )
            {
		        avalID[n] = NULL;
            }

            // Allocate a collision registry.
            colRegistry = new CCollisionRegistry;

            try
            {
	            numIPL = 0;
	            numIDE = 0;

	            if ( preserveMainWorldIntegrity )
	            {
                    // Initialize the main model array
		            memset( modelLOD, 0, sizeof(modelLOD) );

		            // Load all GTA:SA static scene objects (hack)
                    CFileTranslator *replaceRoot = fileSystem->CreateTranslator( "gamedata/rplipl/" );

                    if ( replaceRoot )
                    {
                        replaceRoot->ScanDirectory(
                            "", "*.ipl", true, NULL,
                            [&]( const filePath& path )
                            {
                                LoadReplaceIPL( path );
                            }, NULL
                        );

                        delete replaceRoot;
                    }
                    else
                    {
                        throw converter_runtime_error( "Could not find any GTA:SA item placement information (rplipl/*.ipl)" );
                    }
	            }

                // We should load dynamic object properties.
                {
                    std::unique_ptr <DynObjConfig> objconf( DynObjConfig::LoadConfig( "gamedata/object.dat" ) );

                    if ( !objconf )
                    {
                        printf( "warning: cannot find dynamic object config\n" );
                    }

                    // To calculate LOD model relevance, we should load all replace instances
                    // of the original GTA:SA map.
                    {
                        std::unique_ptr <CFileTranslator> replaceRoot( fileSystem->CreateTranslator( "gamedata/rplide/" ) );

	                    if ( replaceRoot )
                        {
                            replaceRoot->ScanDirectory(
                                "", "*.ide", true, NULL,
                                [&]( const filePath& path )
                                {
                                    LoadReplaceIDE( path, objconf.get(), ignoreFlagsSecure );
                                }, NULL
                            );
                        }
                        else
                        {
                            throw converter_runtime_error( "Could not find GTA:SA model definitions" );
                        }
                    }
                }

	            // We scan through all ipl files and load em
                {
                    std::unique_ptr <CFileTranslator> iplRoot( fileSystem->CreateTranslator( "ipl/" ) );

                    if ( iplRoot )
                    {
                        iplRoot->ScanDirectory(
                            "", "*.ipl", true, NULL,
                            [&]( const filePath& path )
                            {
                                LoadTargetIPL( path );
                            }, NULL
                        );
                    }
                    else
                    {
		                throw converter_runtime_error( "Could not find any item placement files (ipl/*.ipl)" );
                    }
                }

	            // Now proceed through all .ide files
                {
                    std::unique_ptr <CFileTranslator> ideRoot( fileSystem->CreateTranslator( "ipl/" ) );

	                if ( ideRoot )
	                {
                        ideRoot->ScanDirectory(
                            "", "*.ide", true, NULL,
                            [&]( const filePath& path )
                            {
                                LoadTargetIDE( path );
                            }, NULL
                        );
	                }
                    else
	                {
		                throw converter_runtime_error( "Could not find any model definitions (ipl/*.ide)" );
	                }
                }

                // Acquire access.
                std::shared_ptr <CFile> archiveOutputStream;
                std::shared_ptr <CArchiveTranslator> archiveOutputRoot;
                std::shared_ptr <CFileTranslator> outputRoot;

                // Attempt to create a .zip archive.
                if ( zipOutput )
                {
                    const char *outputZipName = zipName;

                    if ( !outputZipName )
                    {
                        outputZipName = "output.zip";
                    }

                    archiveOutputStream.reset( fileRoot->Open( outputZipName, "rb+" ) );

                    if ( archiveOutputStream )
                    {
                        archiveOutputRoot.reset( fileSystem->OpenArchive( *archiveOutputStream ) );

                        if ( !archiveOutputRoot )
                        {
                            archiveOutputStream.reset();
                        }
                    }

                    if ( !archiveOutputStream )
                    {
                        archiveOutputStream.reset( fileRoot->Open( outputZipName, "wb" ) );

                        if ( archiveOutputStream )
                        {
                            archiveOutputRoot.reset( fileSystem->CreateZIPArchive( *archiveOutputStream ) );

                            if ( !archiveOutputRoot )
                            {
                                archiveOutputStream.reset();
                            }
                        }
                    }

                    // Attempt to set the output root.
                    outputRoot = archiveOutputRoot;
                }

                // If we do not have an output root already.
                if ( !outputRoot )
                {
	                // Set up the directory scheme
                    fileRoot->CreateDir( "output/" );

                    outputRoot.reset( fileSystem->CreateTranslator( "output/" ) );
                }

                std::shared_ptr <CFile> archiveResourceStream;
                std::shared_ptr <CArchiveTranslator> archiveResourceRoot;
                std::shared_ptr <CFileTranslator> resourceRoot;

                // Try to find zipped resources (if the config allows their loading).
                if ( zipResources )
                {
                    archiveResourceStream.reset( fileRoot->Open( "resources.zip", "rb" ) );

                    if ( archiveResourceStream )
                    {
                        archiveResourceRoot.reset( fileSystem->OpenArchive( *archiveResourceStream ) );

                        if ( !archiveResourceRoot )
                        {
                            archiveResourceStream.reset();
                        }
                    }

                    resourceRoot = archiveResourceRoot;
                }

                // Default to a directory on the root filesystem.
                if ( !resourceRoot )
                {
                    resourceRoot.reset( fileSystem->CreateTranslator( "resources/" ) );
                }

                g_resourceRoot = resourceRoot.get();
	            g_outputRoot = outputRoot.get();

                if ( !g_resourceRoot )
                {
                    printf( "warning: resource root could not be found\n" );
                }

                try
                {
                    // Load all collision containers.
                    if ( resourceRoot )
                    {
                        printf( "Initializing collision containers...\n" );

                        resourceRoot->ScanDirectory(
                            "@", "*.col", false, NULL,
                            [&]( const filePath& path )
                            {
                                colRegistry->AddContainer( resourceRoot.get(), path );
                            }, NULL
                        );

                        printf( "done!\n" );
                    }

                    if ( g_outputRoot )
                    {
                        g_outputRoot->CreateDir( "models/" );
                        g_outputRoot->CreateDir( "textures/" );
                        g_outputRoot->CreateDir( "coll/" );

	                    // Branch to the handler
	                    if ( !mode || stricmp( mode, "green" ) == 0 )
                        {
		                    success = bundleForGREEN( config.get() );
                        }
                        else if ( stricmp( mode, "eirfork" ) == 0 )
                        {
                            success = bundleForEIRFORK( config.get() );
                        }
                        else
                        {
                            success = bundleForBLUE( config.get() );
                        }
                    }
                    else
                    {
                        throw converter_runtime_error( "failed to acquire output folder link" );
                    }
                }
                catch( ... )
                {
                    g_resourceRoot = NULL;
                    g_outputRoot = NULL;

                    throw;
                }

                // Save output data (if necessary)
                if ( archiveOutputRoot )
                {
                    printf( "writing to .zip archive (do not close)...\n" );

                    archiveOutputRoot->Save();

                    printf( "finished!\n" );
                }

                // Clean up FileSystem links.
                g_resourceRoot = NULL;
                g_outputRoot = NULL;
            }
            catch( ... )
            {
                delete colRegistry;

                throw;
            }

            // Delete the collision registry.
            delete colRegistry;
        }
        catch( converter_runtime_error& error )
        {
            printf( "ERROR: %s\n", error.getMessage() );

#ifdef _WIN32
            getchar();
#endif //_WIN32

            success = false;
        }
        catch( ... )
        {
            printf( "unknown error\n" );

#ifdef _WIN32
            getchar();
#endif

            success = false;
        }
    }

    // Clean up the fileSystem module activity.
    CFileSystem::Destroy( fileSystem );

    return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

CFileTranslator* AcquireResourceRoot( void )
{
    return g_resourceRoot;
}

CFileTranslator* AcquireOutputRoot( void )
{
    return g_outputRoot;
}

const char* GetGenericScriptHeader( void )
{
    return "-- Compiled by Scene2Res, a MTA:SA GTA III engine map importer";
}

const char* GetCompilatorName( void )
{
    if ( compilator == NULL )
        return "The_GTA";

    return compilator;
}